package com.atguigu.gmall.gateway.filter;

import com.atguigu.gmall.common.consts.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.IpUtil;
import com.atguigu.gmall.common.util.JsonUtil;
import com.atguigu.gmall.gateway.properties.PathProperties;
import com.atguigu.gmall.model.user.entity.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.*;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.time.Duration;
import java.util.List;

/**
 * @Author:lyd
 **/
@Component
@Slf4j
public class AuthGlobalFilter implements GlobalFilter {
    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    PathProperties pathProperties;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String ipAddress = IpUtil.getGatwayIpAddress(request);
        String url = request.getURI().getPath();

        //1、判断是否是白名单路径
        if (isWhiteListPath(url, pathProperties.getWhiteListPath())) {
            return doFilter(exchange, chain);
        }
        //2、判断是否是内部路径

        if (isInnerPath(url, pathProperties.getDenyUrl())) {
            return accessDeny(exchange, "没有权限访问该路径");
        }
        //3、判断是否是登录路径
        List<String> authPath = pathProperties.getAuthPath();
        AntPathMatcher matcher = new AntPathMatcher();
        long count = authPath.stream()
                .filter(item -> matcher.match(item, url))
                .count();
        if (count > 0) {
            UserInfo userInfo = getUserInfo(exchange);
            if (userInfo == null || !userInfo.getDeviceId().equals(ipAddress)) {
                //redis中没有用户信息或者token出错，重定向当登录页面；防止令牌复制盗用
                log.warn("用户没有登录，请先登录");
                String loginUrl = pathProperties.getLoginUrl();
                String location = loginUrl + "?originUrl=" + request.getURI().toString();
                return sendRedirect(exchange, location);

            } else {
                //redis有用户信息，并且设备id匹配
                return doFilter(exchange, chain);
            }
        }
        //4、 普通请求：预检查。 你没带令牌往下放，带了令牌，顺便验一下。
        Mono mono = isNormalRequest(exchange, chain, ipAddress);
        return mono;
    }

    private Mono isNormalRequest(ServerWebExchange exchange, GatewayFilterChain chain, String ipAddress) {
        ServerHttpRequest request = exchange.getRequest();
        String userToken = getToken(request);
        if (StringUtils.isEmpty(userToken)) {
            //没带令牌往下放
            return doFilter(exchange, chain);
        }
        //token有值,校验是否正确,从redis中能否查询对应用户信息
        UserInfo userInfo = getUserInfoByToken(userToken);
        if (userInfo == null || !userInfo.getDeviceId().equals(ipAddress)) {
            //redis中没有用户信息或者token出错，重定向当登录页面;防止令牌复制盗用
            String loginUrl = pathProperties.getLoginUrl();
            String location = loginUrl + "?originUrl=" + request.getURI().toString();
            return sendRedirect(exchange, location);
        } else {
            return doFilter(exchange, chain);
        }
    }

    private UserInfo getUserInfoByToken(String token) {
        if (token == null) {
            return null;
        }
        String userJsonStr = getUserJsonStr(token);
        if (StringUtils.hasText(userJsonStr)) {
            return JsonUtil.strToObj(userJsonStr, UserInfo.class);
        }
        return null;
    }


    private UserInfo getUserInfo(ServerWebExchange exchange) {
        String token = getToken(exchange.getRequest());
        return getUserInfoByToken(token);
    }

    private String getUserJsonStr(String token) {
        String userJsonStr = redisTemplate.opsForValue().get(RedisConst.LOGIN_USER + token);
        if (StringUtils.isEmpty(userJsonStr)) {
            //错误的token，查不到用户信息
            return null;
        }
        return userJsonStr;
    }

    private static Mono<Void> sendRedirect(ServerWebExchange exchange, String location) {
        log.info("重定向到：{}", location);
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.FOUND); // 302
        response.getHeaders().setLocation(URI.create(location));
        // 构建立即失效的 Cookie
        ResponseCookie invalidCookie = ResponseCookie
                .from("token", "")
                .path("/")
                .secure(true)   // 生产环境必选
                .httpOnly(true)   // 防 XSS
                .maxAge(Duration.ZERO) // 立即过期
                .sameSite("Strict")
                .build();
        response.addCookie(invalidCookie);
        return response.setComplete();
    }

    private static String getToken(ServerHttpRequest request) {
        MultiValueMap<String, HttpCookie> cookies = request.getCookies();
        HttpCookie cookie = cookies.getFirst("token");
        String token;
        if (!StringUtils.isEmpty(cookie)) {
            token = cookie.getValue();
        } else {
            HttpHeaders headers = request.getHeaders();
            token = headers.getFirst("token");
        }
        if (!StringUtils.hasText(token)) {
            return null;
        }
        return token;
    }

    private static boolean isInnerPath(String url, List<String> denyUrl) {
        AntPathMatcher matcher = new AntPathMatcher();
        long count = denyUrl.stream()
                .filter(item -> matcher.match(item, url))
                .count();
        if (count > 0) {
            return true;
        } else return false;
    }

    private Mono<Void> accessDeny(ServerWebExchange exchange, String msg) {
        log.error("[没有权限访问!]请求路径：{}", exchange.getRequest().getPath());
        ServerHttpResponse response = exchange.getResponse();
        //封装返回数据
        Result<String> result = new Result<>();
        result.setMessage(msg);
        result.setCode(ResultCodeEnum.PERMISSION.getCode());
        //设置响应头类型为json
        response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);

        String jsonStr = JsonUtil.objToJsonStr(result);

        DataBuffer buffer = response.bufferFactory().wrap(jsonStr.getBytes());
        return response.writeWith(Mono.just(buffer));
    }

    private static boolean isWhiteListPath(String url, List<String> whiteListPath) {
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        for (String pattern : whiteListPath) {
            if (antPathMatcher.match(pattern, url)) {
                return true;
            }
        }
        return false;
    }

    private static Mono<Void> doFilter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getURI().getPath();
        log.info("请求路径：{}", path);
        //放行请求
        Mono<Void> mono = chain.filter(exchange)
                .doFinally(t -> log.info("请求结束：{}", path));
        return mono;
    }
}
